package org.vaadinpa.logic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;

import org.vaadinpa.data.Planet;
import org.vaadinpa.data.Universe;
import org.vaadinpa.exceptions.TickOccuredException;
import org.vaadinpa.gui.VaadinPA;
import org.vaadinpa.logic.commands.AsteroidScanCommand;
import org.vaadinpa.logic.commands.AsteroidScanCreateCommand;
import org.vaadinpa.logic.commands.FleetMoveCommand;
import org.vaadinpa.logic.commands.PlanetaryScanCommand;
import org.vaadinpa.logic.commands.PlanetaryScanCreateCommand;
import org.vaadinpa.logic.commands.ShipBuildCommand;
import org.vaadinpa.logic.commands.TechAdvanceCommand;
import org.vaadinpa.logic.commands.TickCommand;

public class CommandHandler {

	/**
	 * Commands to be executed during next tick
	 */
	private static Map<Class<? extends Command>, Queue<Command>> commands = new HashMap<Class<? extends Command>, Queue<Command>>();

	/**
	 * Commands that are created during execution of tick. Added to
	 * commands-list after tick is completed.
	 */
	private static Queue<Command> storedCommands = new LinkedList<Command>();

	/**
	 * Commands that have been executed
	 */
	public static List<Command> executedCommands = new LinkedList<Command>();

	private static boolean tickInProgrogress = false;

	private static long lastTickTime = -1L;

	/**
	 * Stores {@link Command}s to be executed during next tick
	 */
	public static void handle(Command command) {

		/*
		 * Method is not synchronized to allow recursive calling; executing
		 * commands may create new commands that are added after the tick, in
		 * order of entry
		 */

		if (tickInProgrogress) {
			// add to temp queue
			synchronized (storedCommands) {
				storedCommands.offer(command);
			}
		} else {

			if (command instanceof TickCommand) {

				tickInProgrogress = true;
				if (lastTickTime > 0) {
					long timeInBetween = System.currentTimeMillis() - lastTickTime;
					System.out.println("Last ticked " + timeInBetween / 1000 + " seconds ago");
				}
				lastTickTime = System.currentTimeMillis();

				/** advance clock */
				Ticker.currentTick++;

				System.out.println("Tick " + Ticker.currentTick + " starting");

				/** commands before battle */

				// ship build
				exec(ShipBuildCommand.class);

				// asteroid scans
				exec(AsteroidScanCreateCommand.class);
				exec(AsteroidScanCommand.class);

				// fleet movement
				exec(FleetMoveCommand.class);

				/** BATTLE */
				BattleCalculator.checkForAndPerformBattles();

				// check fleets that are destroyed
				Set<Command> invalid = new HashSet<Command>();
				synchronized (storedCommands) {

					for (Command cmd : storedCommands) {
						if (cmd instanceof FleetMoveCommand) {
							FleetMoveCommand c = (FleetMoveCommand) cmd;
							if (c.fleet.isEmpty()) {
								invalid.add(c);
								c.resetFleet();
							}
						}
					}
					storedCommands.removeAll(invalid);
				}

				/** Commands after battle */

				// tech level
				exec(TechAdvanceCommand.class);

				// update resources
				for (Planet p : Universe.instance.values()) {
					p.updateResources();
				}

				// planetary scores
				for (Planet p : Universe.instance.values()) {
					p.updateTotalScore();
				}

				// planetary scans
				exec(PlanetaryScanCreateCommand.class);
				exec(PlanetaryScanCommand.class);

				/** tick processing complete */

				// add commands generated by execution of Commands in this
				// tick
				while (!storedCommands.isEmpty()) {
					addCommand(storedCommands.poll());
				}

				System.out.println("Tick " + Ticker.currentTick + " completed");

				Ticker.fireTickEvent();

				// try {
				// PASerializer.serialize();
				// } catch (IOException e) {
				// e.printStackTrace();
				// }

				tickInProgrogress = false;
			} else {
				addCommand(command);
			}
		}
	}

	private synchronized static void exec(Class<? extends Command> clazz) {
		Queue<Command> q = commands.get(clazz);
		while (q != null && !q.isEmpty()) {
			Command c = q.poll();
			System.out.println("Executing " + clazz.getSimpleName() + ": " + c.toString());
			c.execute();
			c.executed = true;
			executedCommands.add(c);
		}

	}

	private synchronized static void addCommand(Command c) {
		Queue<Command> q = commands.get(c.getClass());
		if (q == null) {
			q = new LinkedList<Command>();
			commands.put(c.getClass(), q);
		}
		q.offer(c);
	}

	public synchronized static void cancel(Command c) throws TickOccuredException {

		Queue<?> queue = commands.get(c.getClass());
		if (queue != null && queue.contains(c)) {

			try {
				c.cancel();
				queue.remove(c);
			} catch (UnsupportedOperationException e) {
				c.cancelNextTick = true;
			}

		} else {
			// command has already been executed
			throw new TickOccuredException();
		}
	}

	/**
	 * Get all non-executed {@link Command}s for the current planet that matches
	 * given types
	 */
	public static List<Command> getEvents(Class<? extends Command>... clz) {

		if (VaadinPA.getCurrent() == null) {
			throw new RuntimeException();
		}

		List<Command> commandsToSend = new ArrayList<Command>();

		for (Class<? extends Command> comClass : clz) {
			Queue<Command> q = commands.get(comClass);
			if (q == null) {
				// no commands
				continue;
			}
			for (Command command : q) {
				if (command.planet.equals(VaadinPA.getPlanet())) {
					commandsToSend.add(command);
				}
			}

		}
		return commandsToSend;
	}

	public static List<FleetMoveCommand> getAllFleetMoveCommands() {
		List<FleetMoveCommand> commandsToSend = new ArrayList<FleetMoveCommand>();

		Queue<Command> q = commands.get(FleetMoveCommand.class);
		if (q == null) {
			// no commands
			return commandsToSend;
		}
		for (Command command : q) {
			commandsToSend.add((FleetMoveCommand) command);
		}

		return commandsToSend;
	}

	public static synchronized List<Command> getWaitingCommands() {
		List<Command> list = new ArrayList<Command>();
		for (Entry<Class<? extends Command>, Queue<Command>> e : commands.entrySet()) {
			for (Command c : e.getValue()) {
				list.add(c);
			}
		}
		return list;
	}

}
